const Redis = require('ioredis')
const genericPool = require('generic-pool')
const {env} = require('../config')
const O = require('../utils/O')

// // ioredis supports all Redis commands:
// redis.set("foo", "bar"); // returns promise which resolves to string, "OK"
// // the format is: redis[SOME_REDIS_COMMAND_IN_LOWERCASE](ARGUMENTS_ARE_JOINED_INTO_COMMAND_STRING)
// // the js: ` redis.set("mykey", "Hello") ` is equivalent to the cli: ` redis> SET mykey "Hello" `
// // ioredis supports the node.js callback style
// redis.get("foo", function (err, result) {
//   if (err) {
//     console.error(err);
//   } else {
//     console.log(result); // Promise resolves to "bar"
//   }
// });
// // Or ioredis returns a promise if the last argument isn't a function
// redis.get("foo").then(function (result) {
//   console.log(result);
// });
// redis.del("foo");
// // Arguments to commands are flattened, so the following are the same:
// redis.sadd("set", 1, 3, 5, 7);
// redis.sadd("set", [1, 3, 5, 7]);
// redis.spop("set"); // Promise resolves to "5" or another item in the set
// // Most responses are strings, or arrays of strings
// redis.zadd("sortedSet", 1, "one", 2, "dos", 4, "quatro", 3, "three");
// redis.zrange("sortedSet", 0, 2, "WITHSCORES").then((res) => console.log(res)); // Promise resolves to ["one", "1", "dos", "2", "three", "3"] as if the command was ` redis> ZRANGE sortedSet 0 2 WITHSCORES `
// // Some responses have transformers to JS values
// redis.hset("myhash", "field1", "Hello");
// redis.hgetall("myhash").then((res) => console.log(res)); // Promise resolves to Object {field1: "Hello"} rather than a string, or array of strings
// // All arguments are passed directly to the redis server:
// redis.set("key", 100, "EX", 10); // set's key to value 100 and expires it after 10 seconds
// // Change the server configuration
// redis.config("set", "notify-keyspace-events", "KEA");

// Redis Stream 是 Redis 5.0 版本新增加的数据结构
// Redis Stream 主要用于消息队列（MQ，Message Queue），Redis 本身是有一个 Redis 发布订阅(pub / sub) 来实现消息队列的功能，但它有个缺点就是消息无法持久化，如果出现网络断开、Redis 宕机等，消息就会被丢弃。
// 简单来说发布订阅(pub / sub) 可以分发消息，但无法记录历史消息。
// 而 Redis Stream 提供了消息的持久化和主备复制功能，可以让任何客户端访问任何时刻的数据，并且能记住每一个客户端的访问位置，还能保证消息不丢失。
// // you may find this read https://redis.io/topics/streams-intro
// // very helpfull as a starter to understand the usescases and the parameters used
// const f = async function () {
//   const channel = "ioredis_channel";
//   // specify the channel. you want to know how many messages
//   // have been written in this channel
//   let messageCount = await redis.xlen(channel);
//   console.log(
//     `current message count in channel ${channel} is ${messageCount} messages`
//   );
//   // specify channel to write a message into,
//   // messages are key value
//   const myMessage = "hello world";
//   await redis.xadd(channel, "*", myMessage, "message");
//   messageCount = await redis.xlen(channel);
//   console.log(
//     `current message count in channel ${channel} is ${messageCount} messages`
//   );
//   // now you can see we have one new message
//   // use xread to read all messages in channel
//   let messages = await redis.xread(["STREAMS", channel, 0]);
//   messages = messages[0][1];
//   console.log(
//     `reading messages from channel ${channel}, found ${messages.length} messages`
//   );
//   for (let i = 0; i < messages.length; i++) {
//     let msg = messages[i];
//     msg = msg[1][0].toString();
//     console.log("reading message:", msg);
//   }
//   process.exit(0);
// };
// f();

//redis 连接参数设置 https://github.com/luin/ioredis/blob/HEAD/API.md
var redis_connect_option = {
  'host': env.REDIS.HOST,
  'port': env.REDIS.PORT || 6379,
  'db': env.REDIS.DB || 0,
  'password': '123456',
  //'retry_max_delay': 3000,
  //'max_attempts' : redis_max_attempts,
  'autoResendUnfulfilledCommands': true,
  'keyPrefix': 'rainbow:',
  'retryStrategy': function (times) {
    // if (times % 10 == 0) {
    //   //重试次数 每10次刷新页面
    //   io.sockets.emit("refreshPage");
    // }
    // reconnect after
    return Math.min(times * 100, 3000);
  },
  'reconnectOnError': function (err) {
    console.log("==== redis_pool_client reconnectOnError:", err);
  }
};

let num = 0;
(function(){
  num ++
  console.log('==== run ----   ! ', num);
  // 注意: 启动一个连接来订阅 命令
  // bug : 实际为2个
  const redisMonitor = new Redis(redis_connect_option)
  redisMonitor.monitor()
  .then(function (monitor) {
    console.log('==== redis monitor ready ! ');
    monitor.on("monitor", function (time, args, source, database) {
      console.log('----- redis: ', database, time, args);
      // console.log(source, database, time , args);
    });
  }).catch(err => {
    console.log('==== redis monitor start fail : ', err);
  });
})()


// 创建redis连接池---begin 需要占用一个连接
var redis_factory = {
  create: function () {
    client = new Redis(redis_connect_option);
    client.on("disconnect", function () {
      console.log("==== redis_pool_client disconnect");
    });
    client.on("connect", function () {
      console.log("==== redis_pool_client connect");
    });
    // 注意: 所有连接都监听了
    // supports promise as well as other commands
    client.monitor().then(function (monitor) {
      monitor.on("monitor", function (time, args, source, database) {
        console.log('----- redis: ',database, time , args);
        // console.log(source, database, time , args);
      });
    });
    return client;
  },
  destroy: function (client) {
    client.disconnect()
  }
};

// bug : 实际最大为2倍
var redis_opts = {
  max: 5 // maximum size of the pool
  ,min: 0 // minimum size of the pool
  ,autostart: true //是否开启启动 否则懒启动
};
var redisPool = genericPool.createPool(redis_factory, redis_opts);
redisPool.on('factoryCreateError', function (err) {
  //log stuff maybe
  console.log("redis_pool_client factoryCreateError" , err);
});

redisPool.on('factoryDestroyError', function (err) {
  //log stuff maybe
  console.log("redis_pool_client factoryDestroyError" , err);
});

//创建redis连接池---end
// redisPool.acquire()
// 下面不需要了 自动关掉了
// 进程退出时自动关闭连接池
// process.on('exit', (code) => {
//   console.log('==== close redis pool ', code);
//   redisPool.drain().then(function () {
//     redisPool.clear();
//   });
// })
const OK    = 'OK'
const redis = {
  /**
   * const conn = await Redis.getConn(true,true) // Redis.Pipeline
   * const conn = await Redis.getConn(true) // OK
   * const conn = await Redis.getConn() // Redis.Redis
   * @return  Redis.Redis     if not trans
   * @param   boolen          if trans no wait
   * @return  Redis.Pipeline  if trans and wait
   */
  async getConn(trans = false,wait=false) {
    let conn = await redisPool.acquire()
    if(trans){
      // 事务 需要同一个连接
      if(wait){
        // 等待一起执行
        // return Redis.Pipeline
        return conn.multi({ pipeline: true })
      }else{
        // 每步立即执行
        const ret = await conn.multi({ pipeline: false })
        return ret === OK
      }
    }
    // return Redis.Redis
    return conn
  },
  /**
   * @param   string          func
   * @param   any[]|...any[]  params
   * @return  Promise<{func}ByConn的返回值>
   */
  async wrapConn(func, ...params) {
    const conn = await this.getConn()
    const val = this[func + 'ByConn'](conn, ...params)
    this.end(conn)
    return val
  },
  /**
   * @param   Redis.Redis  conn   [conn description]
   * @param   boolean      trans  [trans description]
   * @return  [Error | null, string]  in single trans
   * @return  [Error | null, any]     in pipeline trans
   * @return  void                    not in trans
   */
  async end(conn, trans = false) {
    if (trans) {
      ret = await conn.exec()
      redisPool.release(conn)
      return ret
    }
    redisPool.release(conn)
  },


  pipeline( commands) {
    return this.wrapConn('pipeline',commands)
  },
  /**
   * @param  commands any[]|any[][]
   * ["set", "foo", "bar"]
   * [
   *  ["set", "foo", "bar"],
   *  ["get", "foo"],
   * ]
   * @return  Promise<any[][eachErr,eachResult]>
   */
  pipelineByConn(conn, commands) {
    O.isArray(commands,':commands')
    if(typeof commands[0] == 'string'){
      commands = [commands]
    }
    return conn.pipeline(commands).exec()
  },
  // 事务
  multi(commands) {
    return this.wrapConn('multi', commands)
  },
  /**
   * 事务
   * @param  commands any[]|any[][]
   * ["set", "foo", "bar"]
   * [
   *  ["set", "foo", "bar"],
   *  ["get", "foo"],
   * ]
   * @return  Promise<any[][eachErr,eachResult]>
   */
   multiByConn(conn, commands) {
    O.isArray(commands, ':commands')
    if (typeof commands[0] == 'string') {
      commands = [commands]
    }
    return conn.multi(commands).exec()
  },


  set(k,v,exp = 0){
    return this.wrapConn('set',k,v,exp)
  },
  // Promise<'OK'>
  setByConn(conn,k, v, exp = 0) {
    exp = Math.max(exp, 0)
    let ret
    if (exp) {
      ret = conn.set(k, JSON.stringify(v), 'EX', exp)
    } else {
      // -1 永久存在,除非lcu 或 停机
      ret = conn.set(k, JSON.stringify(v))
    }
    this.end(conn)
    return ret
  },
  get( k , raw=false) {
    return this.wrapConn('get',k,raw)
  },
  /**
   * @return  Promise<string|null>
   */
  async getByConn(conn, k , raw=false) {
    const ret = await conn.get(k);
    return raw ? ret : JSON.parse(ret);
  },


  setbit( k, posBit, bool) {
    return this.wrapConn('setbit',k,posBit,bool)
  },
  /**
   * 设置/清除 一个字符串的bit位
   *  offset 理论上支持 : string <512M <2^32
   *  eg: sign:uid 今年第几天 1/0
   *  eg: online uid 1/0  : 3亿用户只需要36MB的空间
   *  @return  Promise<preBit:1|0>   preBit: 执行前的bit值
   */
  setbitByConn(conn,k,posBit,bool){
    bool = bool==='0' ? false : !!bool
    if (O.isNumberString(posBit)) {
      posBit = parseInt(posBit)
    }
    const max = 8*1024
    O.isTrue(typeof posBit != 'number' || posBit<0 || posBit> max,`redis.bit only assist number:0~${max}, but get: ${typeof posBit}(${posBit})`)
    return conn.setbit(k, 8 * posBit, bool ? 1 : 0)
  },
  getbit(k,posBit){
    return this.wrapConn('getbit',k,posBit)
  },
  /**
   * @return Promise<number:1|0>
   */
  getbitByConn(conn,k,posBit){
    if(O.isNumberString(posBit)){
      posBit = parseInt(posBit)
    }
    const max = 8 * 1024
    O.isTrue(typeof posBit != 'number' || posBit < 0 || posBit > max, `redis.bit only assist number:0~${max}, but get: ${typeof posBit}(${posBit})`)
    return conn.getbit(k,8*posBit)
  },
  bitcount(k,startBtye=false,endByte=false){
    return this.wrapConn('bitcount', k, startBtye,endByte)
  },
  /**
   * @return Promise<number:0+>
   */
  bitcountByConn(conn,k,startBtye=false,endByte=false){
    console.log('this' , typeof this.wrapConn);
    if (startBtye){
      return conn.bitcount(k, startBtye, endByte)
    }else{
      return conn.bitcount(k)
    }
  },
}
module.exports = redis

// module.exports = redisPool
// exports.getConn = getConn